package com.gieee.ywj.of.rest.registrar;

import com.gieee.ywj.of.anno.EnableFeignClientByMe;
import com.gieee.ywj.of.anno.RestClient;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.stream.Stream;

/**
 * @author wenjie.yang on 2019/7/29.
 */
public class RestClientsRegistrar implements ImportBeanDefinitionRegistrar,BeanFactoryAware, EnvironmentAware {

    private BeanFactory beanFactory;

    private Environment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
                                        BeanDefinitionRegistry registry) {

        ClassLoader classLoader = this.getClass().getClassLoader();
        Map<String, Object> attributes =
                importingClassMetadata.getAnnotationAttributes(EnableFeignClientByMe.class.getName());
        //将@RestClienr接口的代理实现 注册为Bean
        Class<?>[] clazzs = (Class[])attributes.get("clients");
        //筛选所以得接口
        Stream.of(clazzs).filter(Class::isInterface)//过滤接口类型
                .filter(clazz-> AnnotationUtils.findAnnotation(clazz, RestClient.class)!=null)//过滤 RestClient
                .forEach(interfaceClazz ->{
                    //@RestClient 接口变成 JDK动态代理
                    RestClient restClient = AnnotationUtils.findAnnotation(interfaceClazz, RestClient.class);
                    //获取应用名称    使用 environment api 解析 ${} 处理占位符
                    String targetServiceName = environment.resolvePlaceholders(restClient.name());
                    //RestTemplate -> serviceName/uri?param=...

                    Object proxy = Proxy.newProxyInstance(classLoader, new Class[]{interfaceClazz},
                            new RestInvocationHandler(targetServiceName,beanFactory)
                    );
                    /**
                     * <bean class = 'user>
                     *  <constructor-args>${name}</constructor-args>
                     * </bean>
                     */
                    String beanName = "RestClient" + interfaceClazz.getName();
                    //以下 两种方法 都可以
                    if(registry instanceof SingletonBeanRegistry){
                        //使用SingletonBeanRegistry 来注册
                        ((SingletonBeanRegistry) registry).registerSingleton(beanName,proxy);
                    }else {
                        BeanDefinitionBuilder beanDefinitionBuilder= BeanDefinitionBuilder.genericBeanDefinition(RestClientClassFactoryBean.class);
                        //增加第一个构造器参数引用
                        beanDefinitionBuilder.addConstructorArgValue(proxy);
                        beanDefinitionBuilder.addConstructorArgValue(interfaceClazz);
                        //把 代理类 注册到  spring 容器中
                        registry.registerBeanDefinition(beanName,beanDefinitionBuilder.getBeanDefinition());
                    }
                });
        //接口变代理
    }


    /**
     * 因为 被代理 对象是接口  所以只能使用FactoryBean 给他 注入到 Spring 容器中
     */
    private static class RestClientClassFactoryBean implements FactoryBean{
        private final Class<?> restClient;
        private final Object proxy;

        public RestClientClassFactoryBean(Object proxy,Class<?> restClient) {
            this.restClient = restClient;
            this.proxy = proxy;
        }
        @Override
        public Object getObject() throws Exception {
            return proxy;
        }
        @Override
        public Class<?> getObjectType() {
            return restClient;
        }
        @Override
        public boolean isSingleton() {
            return false;
        }
    }

    private static class RestInvocationHandler implements  InvocationHandler{
        private String targetServiceName;
        private BeanFactory beanFactory;
        public RestInvocationHandler(String targetServiceName,BeanFactory beanFactory){
            this.targetServiceName = targetServiceName;
            this.beanFactory = beanFactory;
        }
        private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            GetMapping requestMapping = AnnotationUtils.findAnnotation(method, GetMapping.class);
            if(requestMapping!=null){
                //获取 uri
                String[] uri = requestMapping.value();
                // http://${serviceName}/uri
                StringBuilder urlBuilder = new StringBuilder("http://").append(targetServiceName).append(uri[0]);
                //获取 参数总数
                int count = method.getParameterCount();
                Class<?>[] parameterTypes = method.getParameterTypes();
                //获取参数
                String[] parameterNames = nameDiscoverer.getParameterNames(method);
                StringBuilder queryString = new StringBuilder();
                for (int i = 0;i<count;i++){
                    //参数名
                    //uri?name1=v1&name2=v2
                    String parameterName = parameterNames[i];
                    Class<?> parameterType = parameterTypes[i];
                    RequestParam requestParam = (RequestParam)method.getParameterAnnotations()[i][0];
                    String requestParamName = StringUtils.isNotBlank(requestParam.value())?requestParam.value(): parameterName;
                    String requestParamValue = parameterType.equals(String.class)?(String)args[i]:String.valueOf(args[i]);
                    queryString.append(requestParamName).append("=").append(requestParamValue).append("&");
                }
                urlBuilder.append("?").append(queryString.substring(0,queryString.length()-1));
                String requestUrl = urlBuilder.toString();
                //获取 RestTemplate 执行 Bean名称
                //获取 BeanFactory
                RestTemplate loadBalanced = beanFactory.getBean("loadBalanced", RestTemplate.class);
                return loadBalanced.getForEntity(requestUrl, method.getReturnType()).getBody().toString();
            }
            return null;
        }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;

    }
}
